CF2006 div.1

📅Date: 2024-09-05 📚Category: 算法竞赛 📂Tag: Codeforces 📑Word: 7.9k

CF2006 div.1

B Iris and the Tree

原题目结点按 \(\text{dfs}\) 序编号,那么每条边只会被两个询问覆盖,暴力修改统计即可。

\(\text{cnt}\) 表示当前边还未全部出现的询问个数,\(S\) 为当前出现的边权和,则现在的答案为 \((w-S)\times cnt + 2S\)(考虑满的链直接记录总长,未满的链考虑每条边的贡献整理后得到此式)。

下面我们考虑一般情况,当结点编号任意或者给定树上的 \(q\) 条链作为询问时。

不难发现我们仍可以用原题计算答案的方式,如果我们知道当前还没满的边的数量 \(cnt\),已出现的边权和 \(S\),已出现边的贡献 \(sum\),那么答案为 \((w-S)\times cnt +sum\)

下面就来考虑如何求 \(cnt\)\(sum\)

\(sum\) 直接考虑每条边的贡献,即每条边被几个询问覆盖。我们可以对每条链求端点的 \(\text{lca}\) 通过树上差分的方法求出。

\(cnt\) 由于操作可以离线,我们可以定义边权为这条边出现的时间,通过树上倍增(边权下放点权,求链上最值)等方式就可以求出每条链上最晚出线的边的时间。进而得到每条边出现时会导致多少条链满了。

而且由于直接给出父亲,我们甚至不需要建出这棵树,因为倍增和差分都是自下而上更新的。

复杂度:\(O(n\log n)\)

const int N = 2e5 + 10;
int T, n, x[N], f[N][18], g[N][18], cf[N], cnt[N], dep[N];
ll w, y[N], S2, S;
void getlca(int x, int y, int &lca, int &mx)
{
    if (dep[x] < dep[y])
        swap(x, y);
    for (int p = 17; p >= 0; p--)
        if (dep[f[x][p]] >= dep[y])
            mx = max(mx, g[x][p]), x = f[x][p];
    if (x == y)
        return lca = x, void();
    for (int p = 17; p >= 0; p--)
        if (f[x][p] != f[y][p])
        {
            mx = max(mx, max(g[x][p], g[y][p]));
            x = f[x][p], y = f[y][p];
        }
    mx = max(mx, max(g[x][0], g[y][0]));
    return lca = f[x][0], void();
}
int main()
{
    read(T);
    while (T--)
    {
        read(n), read(w);
        for (int i = 1; i <= n; i++)
            cf[i] = cnt[i] = 0;
        for (int i = 2; i <= n; i++)
            read(f[i][0]), dep[i] = dep[f[i][0]] + 1;
        for (int j = 1; j <= 17; j++)
            for (int i = 1; i <= n; i++)
                f[i][j] = f[f[i][j - 1]][j - 1];
        for (int i = 2; i <= n; i++)
            read(x[i]), read(y[i]), g[x[i]][0] = i;
        for (int j = 1; j <= 17; j++)
            for (int i = 1; i <= n; i++)
                g[i][j] = max(g[f[i][j - 1]][j - 1], g[i][j - 1]);
        for (int i = 1, x, y, lca, mx; i <= n; i++)
        {
            x = i, y = i % n + 1;
            cf[x]++;
            cf[y]++;
            mx = 0;
            getlca(x, y, lca, mx);
            cf[lca] -= 2;
            cnt[mx]++;
        }
        for (int i = n; i; i--)
            cf[f[i][0]] += cf[i];
        S = S2 = 0;
        for (int i = 2, sum = n; i <= n; i++)
        {
            sum -= cnt[i];
            S += y[i];
            S2 += y[i] * cf[x[i]];
            write((w - S) * sum + S2, i < n ? ' ' : '\n');
        }
    }
    flushout();
    return 0;
}

C Eri and Expanded Sets

题目大意

对于集合 \(S\) 现有操作,任选 \(S\) 中的两个值不相同但奇偶性相同的数 \(x\)\(y\)\(\dfrac{x+y}{2}\) 加入 \(S\)

我们称一个集合是‘好的’当且仅当这个集合中出现的数是连续的,即 \(|S|=\max\limits_{x\in S}x - \min\limits_{x\in S} x + 1\)

现给定一个长为 \(n\) 的序列 \(\{a_i\}\) 求有多少个子区间 \([l,r]\) 满足将该区间的值取出作为初始 \(S\),再进行若干次操作后能变为‘好的’集合。

题解

如果两个数的差是偶数,我们就能在这两个数中间加入一个新数(之前不存在)。

手玩几组样例不难发现,经过若干次操作之后最终集合一定是一个公差为奇数的等差数列。

因为首先相邻数的差肯定是奇数,不然显然能继续加新数。

其次如果相邻的两个差不相等,不妨设 \(x<y<z,y-x\neq z-y\) 那么对 \(x,z\) 操作得到的数显然不是 \(y\) 也能继续操作。

我们进一步观察相邻差不相等的操做,假设相邻的差分别为 \(d_1<d_2\),那么新增一个数后这两个差将被分成 \(d_1,\frac{d_2-d_1}{2},\frac{d_1+d_2}{2}\)

发现这个操作类似辗转相减法,只是多了个除以 \(2\)。但由于 \(d_1,d_2\) 都是奇数,所以他们的 \(\gcd\) 也是奇数,故 \(\gcd(d_1,d_2)=\gcd(d_1,d_2-d_1)=\gcd(d_1,\frac{d_2-d_1}{2})\)

所以最终的公差就是排序后差分数组的 \(\gcd\),又有排序的 \(\gcd\) 等于乱序的 \(\gcd\)

就是求 \(\{a_i\}\) 的差分数组去除因子 \(2\) 后,区间 \(\gcd\) 等于 \(1\) 的子区间数量。

当我们枚举左端点时,最左端的合法右端点显然是单调不降的,用双指针+线段树/st表维护区间 \(\gcd\) 即可。

除此之外,当区间全相等时也是符合要求的但我们并没有记录,所以还需单独计算所有相等的子区间数量。

复杂度:\(O(n(\log n+\log V))\)

const int N=4e5+10;
int T,n,a[N],tr[N<<2],d[N];
void build(int rt,int l,int r)
{
    if(l==r)return tr[rt]=d[l],void();
    int mid=(l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    tr[rt]=__gcd(tr[ls],tr[rs]);
    return;
}
int res;
int query(int rt,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)return tr[rt];
    int mid=(l+r)>>1;
    if(R<=mid)return query(ls,l,mid,L,R);
    if(mid<L)return query(rs,mid+1,r,L,R);
    return __gcd(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));

}
int main()
{
    read(T);
    while(T--)
    {
        read(n);
        for(int i=1;i<=n;i++)read(a[i]);
        for(int i=1;i<n;i++)
        {
            d[i]=abs(a[i+1]-a[i]);
            while(d[i]%2==0&&d[i])d[i]/=2;
        }
        if(n>1)build(1,1,n-1);
        ll ans=n;
        for(int i=1,r1=1,r2=0;i<n;i++)
        {
            r1=max(r1,i);
            while(r1<n&&query(1,1,n-1,i,r1)!=1)r1++;
            ans+=n-r1;
            r2=max(r2,i-1);
            while(r2<n-1&&d[r2+1]==0)r2++;
            ans+=r2-i+1;
        write(ans);
    }
    flushout();
    return 0;
}

D Iris and Adjacent Products

题解

考虑最优排序一定是大的一半倒序,小的一半正序交错排。

所以只要 \(\forall i \le \sqrt{k}\) 满足大于 \(\frac{n}{i}\) 的数的个数比小于 \(i\) 的数少即可。(取等号的细节自行考虑)。

那么一个显然的想法就是直接开个 \(n\sqrt{k}\) 的数组统计相应数的个数,但被卡空间了。

那么我们可以使用伟大的离线算法‘莫队’,这样我们只需一个 \(\sqrt{k}\) 的数组统计个数即可。

也可以离线对每个 \(i\) 都做一次。

复杂度:\(O(n\sqrt{k})\)

代码:咕咕咕。

评论